package com.dji.sample.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.util.ThumbnailUtil;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
import com.dji.sample.component.websocket.model.BizCodeEnum;
import com.dji.sample.component.websocket.service.IWebSocketMessageService;
import com.dji.sample.manage.model.dto.DeviceDTO;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.service.IDeviceRedisService;
import com.dji.sample.manage.service.IDeviceService;
import com.dji.sample.media.dao.IFileMapper;
import com.dji.sample.media.model.*;
import com.dji.sample.media.service.IFileService;
import com.dji.sample.media.service.IMediaRedisService;
import com.dji.sample.media.service.IMediaService;
import com.dji.sample.wayline.dao.IWaylineJobMapper;
import com.dji.sample.wayline.model.dto.WaylineJobDTO;
import com.dji.sample.wayline.model.entity.WaylineFileEntity;
import com.dji.sample.wayline.model.entity.WaylineJobEntity;
import com.dji.sample.wayline.model.enums.WaylineJobStatusEnum;
import com.dji.sample.wayline.service.IOssFileService;
import com.dji.sample.wayline.service.IWaylineFileService;
import com.dji.sample.wayline.service.IWaylineJobService;
import com.dji.sample.wayline.service.impl.OssFileServiceImpl;
import com.dji.sample.wayline.service.impl.WaylineJobServiceImpl;
import com.dji.sdk.cloudapi.media.*;
import com.dji.sdk.cloudapi.media.api.AbstractMediaService;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import com.dji.sdk.mqtt.MqttReply;
import com.dji.sdk.mqtt.events.TopicEventsRequest;
import com.dji.sdk.mqtt.events.TopicEventsResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URL;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * @author sean
 * @version 0.2
 * @date 2021/12/9
 */
@Service
@Slf4j
public class MediaServiceImpl extends AbstractMediaService implements IMediaService {

    @Autowired
    private IFileService fileService;

    @Autowired
    private WaylineJobServiceImpl waylineJobServiceImpl;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private IDeviceService deviceService;

    @Autowired
    private IWebSocketMessageService webSocketMessageService;

    @Autowired
    private IDeviceRedisService deviceRedisService;

    @Autowired
    private IMediaRedisService mediaRedisService;

    @Autowired
    private IOssFileService ossFileService;

    @Autowired
    private IWaylineJobService jobService;

    @Autowired
    private IWaylineFileService waylineFileService;

    @Autowired
    private IFileMapper fileMapper;

    @Autowired
    private OssServiceContext ossServiceContext;

    @Autowired
    private IWaylineJobMapper waylineJobMapper;

    @Autowired
    private ThumbnailUtil thumbnailUtil;

    @Autowired
    @Qualifier("thumbnailExecutor")
    private Executor thumbnailExecutor;


    @Override
    public Boolean fastUpload(String workspaceId, String fingerprint) {
        return fileService.checkExist(workspaceId, fingerprint);
    }

    @Override
    public Integer saveMediaFile(String workspaceId, MediaUploadCallbackRequest file) {
        return fileService.saveFile(workspaceId, file);
    }

    @Override
    public List<String> getAllTinyFingerprintsByWorkspaceId(String workspaceId) {
        return fileService.getAllFilesByWorkspaceId(workspaceId)
                .stream()
                .map(MediaFileDTO::getTinnyFingerprint)
                .collect(Collectors.toList());
    }

    @Override
    public List<String> getExistTinyFingerprints(String workspaceId, List<String> tinyFingerprints) {
        List<String> tinyFingerprintList = this.getAllTinyFingerprintsByWorkspaceId(workspaceId);
        return tinyFingerprints
                .stream()
                .filter(tinyFingerprintList::contains)
                .collect(Collectors.toList());

    }

    @Override
    public TopicEventsResponse<MqttReply> fileUploadCallback(TopicEventsRequest<FileUploadCallback> request, MessageHeaders headers) {
        FileUploadCallback callback = request.getData();

        if (MqttReply.CODE_SUCCESS != callback.getResult()) {
            log.error("Media file upload failed!");
            return null;
        }

        String jobId = callback.getFile().getExt().getFlightId();
//        FileUploadCallbackFile file = callback.getFile();
//        String mediaPath = file.getPath();
//        String waylineName = mediaPath.substring(mediaPath.lastIndexOf("-") + 1, mediaPath.length());
//        Optional<WaylineJobDTO> jobDTO = jobService.getJobByJobIdWithoutWorkspaceId(jobId);
//        if (jobDTO.isEmpty()) {
//            log.info("这个jobId不存在，将要新建一个jobId");
//            WaylineJobEntity jobEntity = WaylineJobEntity.builder()
//                    .name(waylineName + UUID.randomUUID())
//                    .dockSn("")
//                    .fileId("")
//                    .username("")
//                    .workspaceId("")
//                    .jobId(UUID.randomUUID().toString())
//                    .beginTime(0l)
//                    .endTime(0l)
//                    .status(WaylineJobStatusEnum.SUCCESS.getVal())
//                    .taskType(0)
//                    .waylineType(0)
//                    .outOfControlAction(0)
//                    .rthAltitude(0)
//                    .mediaCount(0)
//                    .build();
//            Optional<WaylineJobDTO> waylineJobDTO = waylineJobServiceImpl.insertWaylineJob(jobEntity);
//        }

        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getGateway());
        MediaFileCountDTO mediaFileCount = mediaRedisService.getMediaCount(request.getGateway(), jobId);
        // duplicate data
        if (deviceOpt.isEmpty()
                || (Objects.nonNull(mediaFileCount) && request.getBid().equals(mediaFileCount.getBid())
                && request.getTid().equals(mediaFileCount.getTid()))) {
            return new TopicEventsResponse<MqttReply>().setData(MqttReply.success());
        }

        DeviceDTO device = deviceOpt.get();
        boolean isSave = parseMediaFile(callback, device);
        if (!isSave) {
            log.error("Failed to save the file to the database, please check the data manually.");
            return null;
        }

        notifyUploadedCount(mediaFileCount, request, jobId, device);
        return new TopicEventsResponse<MqttReply>().setData(MqttReply.success());
    }

    @Override
    public TopicEventsResponse<MqttReply> highestPriorityUploadFlightTaskMedia(
            TopicEventsRequest<HighestPriorityUploadFlightTaskMedia> request, MessageHeaders headers) {
        String jobId = request.getData().getFlightId();
        if (!StringUtils.hasText(jobId)) {
            return null;
        }

        MediaFileCountDTO countDTO = mediaRedisService.getMediaHighestPriority(request.getGateway());
        if (Objects.nonNull(countDTO)) {
            if (jobId.equals(countDTO.getJobId())) {
                mediaRedisService.setMediaHighestPriority(request.getGateway(), countDTO);
                return new TopicEventsResponse<MqttReply>().setData(MqttReply.success());
            }
            countDTO.setPreJobId(countDTO.getJobId());
        }
        countDTO.setJobId(jobId);
        mediaRedisService.setMediaHighestPriority(request.getGateway(), countDTO);

        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getGateway());
        if (deviceOpt.isEmpty()) {
            return null;
        }

        webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.HIGHEST_PRIORITY_UPLOAD_FLIGHT_TASK_MEDIA.getCode(), countDTO);

        return new TopicEventsResponse<MqttReply>().setData(MqttReply.success());
    }

    private Boolean parseMediaFile(FileUploadCallback callback, DeviceDTO device) {
        MediaUploadCallbackRequest file = convert2callbackRequest(callback.getFile());
        // Set the drone sn that shoots the media
        file.getExt().setSn(device.getChildDeviceSn());

        // set path
        String objectKey = file.getObjectKey();
        file.setPath(objectKey.substring(Optional.of(objectKey.indexOf(OssConfiguration.objectDirPrefix))
                        .filter(index -> index > 0).map(index -> index++).orElse(0),
                objectKey.lastIndexOf("/")));
        boolean result = fileService.saveFile(device.getWorkspaceId(), file) > 0;
        if (result) {
            thumbnailExecutor.execute(() -> {
                try {
                    String originKey = file.getObjectKey();
                    log.info("开始生成缩略图，原始图片key: {}", originKey);
                    String smallObjectKey = thumbnailUtil.generateThumbnailUrl(originKey);
                    log.info("缩略图生成成功，URL: {}", smallObjectKey);

                    List<MediaFileEntity> mediaFileEntities = fileMapper.selectList(new LambdaQueryWrapper<MediaFileEntity>().eq(MediaFileEntity::getObjectKey, originKey));
                    for (MediaFileEntity mediaFileEntity : mediaFileEntities) {
                        if (mediaFileEntity != null) {
                            mediaFileEntity.setSmallPhoto(smallObjectKey);
                            int update = fileMapper.updateById(mediaFileEntity);
                            log.info("数据库更新结果: {}, small_photo字段已更新为: {}", update > 0 ? "success" : "failed", smallObjectKey);
                        } else {
                            log.error("未找到对应的媒体文件记录，objectKey: {}", originKey);
                        }
                    }
                } catch (Exception e) {
                    log.error("生成缩略图失败", e);
                }
            });

        }
        return result;
    }

    private void notifyUploadedCount(MediaFileCountDTO mediaFileCount, TopicEventsRequest<FileUploadCallback> request, String jobId, DeviceDTO dock) {
        // Do not notify when files that do not belong to the route are uploaded.
        if (Objects.isNull(mediaFileCount)) {
            return;
        }
        mediaFileCount.setBid(request.getBid());
        mediaFileCount.setTid(request.getTid());
        mediaFileCount.setUploadedCount(mediaFileCount.getUploadedCount() + 1);

        // After all the files of the job are uploaded, delete the media file key.
        if (mediaFileCount.getUploadedCount() >= mediaFileCount.getMediaCount()) {
            mediaRedisService.delMediaCount(request.getGateway(), jobId);

            // After uploading, delete the key with the highest priority.
            MediaFileCountDTO fileCount = mediaRedisService.getMediaHighestPriority(request.getGateway());
            if (Objects.nonNull(fileCount) && jobId.equals(fileCount.getJobId())) {
                mediaRedisService.delMediaHighestPriority(request.getGateway());
            }
        } else {
            mediaRedisService.setMediaCount(request.getGateway(), jobId, mediaFileCount);
        }

        webSocketMessageService.sendBatch(dock.getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.FILE_UPLOAD_CALLBACK.getCode(), mediaFileCount);


    }

    @Override
    public PaginationData<MediaFileVO> getAllMediaFilesPagination(String workspaceId, MediaQueryDTO mediaQueryDTO) {
        PaginationData<MediaFileEntity> paginationData = fileService.queryMediaFileEntitiesPaginationByWorkspaceId(workspaceId, mediaQueryDTO.getDroneSn(), mediaQueryDTO.getWaylineId(), mediaQueryDTO.getTimeStart(), mediaQueryDTO.getTimeEnd(), mediaQueryDTO.getPage(), mediaQueryDTO.getPageSize());
        List<MediaFileVO> voList = paginationData.getList()
                .stream()
                .map(this::convertEntityToVo)
                .collect(Collectors.toList());

        return new PaginationData<>(voList, paginationData.getPagination());
    }

    @Override
    public PaginationData<MediaFileVO> getAllMediaFilesPaginationByJobId(String workspaceId, String jobId, long page, long pageSize) {
        long startTime = System.currentTimeMillis();
        log.info("查询任务文件 - workspaceId: {}, jobId: {}, page: {}, pageSize: {}", workspaceId, jobId, page, pageSize);

        // 1. 查询V类型文件（主要文件）- 分页查询
        Page<MediaFileEntity> pageDataV = fileMapper.selectPage(
                new Page<>(page, pageSize),
                new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                        .eq(MediaFileEntity::getJobId, jobId)
                        .like(MediaFileEntity::getFileName, "%V_%")
                        .orderByDesc(MediaFileEntity::getId));

        List<MediaFileEntity> recordsV = pageDataV.getRecords();
        if (recordsV.isEmpty()) {
            log.info("未找到V文件，直接返回空结果");
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination(pageDataV.getCurrent(), pageDataV.getSize(), pageDataV.getTotal()));
        }

        // 2. 批量查询关联数据，避免重复查询
        // 2.1 获取 Job 信息（只查询一次）
        Optional<WaylineJobDTO> jobDTO = jobService.getJobByJobId(workspaceId, jobId);
        String jobName = "";
        String waylineName = "";
        if (jobDTO.isPresent()) {
            WaylineJobDTO waylineJobDTO = jobDTO.get();
            jobName = waylineJobDTO.getJobName();
            String waylineId = waylineJobDTO.getFileId();
            if (waylineId != null) {
                WaylineFileEntity waylineFile = waylineFileService.getWaylineFileByWaylineId(waylineId);
                if (waylineFile != null) {
                    waylineName = waylineFile.getName();
                }
            }
        }

        // 2.2 批量获取设备名称
        Set<String> droneSnSet = recordsV.stream()
                .map(MediaFileEntity::getDrone)
                .filter(StringUtils::hasText)
                .collect(Collectors.toSet());
        Map<String, String> droneNameCache = batchGetDroneNamesByDroneSns(droneSnSet);

        // 3. 只查询当前页V文件对应的T文件，而不是全量查询
        Set<String> vFileKeys = recordsV.stream()
                .map(this::extractFileKey)
                .collect(Collectors.toSet());

        Map<String, List<MediaFileEntity>> tFileMap = batchGetTFilesByJobId(workspaceId, jobId, vFileKeys);

        // 4. 批量生成所有需要的 OSS URL
        Set<String> allObjectKeys = new HashSet<>();
        recordsV.forEach(v -> {
            allObjectKeys.add(v.getObjectKey());
            allObjectKeys.add(v.getSmallPhoto());
        });
        tFileMap.values().forEach(tFiles -> {
            tFiles.forEach(t -> {
                allObjectKeys.add(t.getObjectKey());
                allObjectKeys.add(t.getSmallPhoto());
            });
        });

        // 6. 生成URL
        if (allObjectKeys.isEmpty()) {
            log.info("没有需要生成URL的文件，直接返回空结果");
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination(pageDataV.getCurrent(), pageDataV.getSize(), pageDataV.getTotal()));
        }
        Map<String, String> urlMap = batchGenerateOssUrls(allObjectKeys);

        // 5. 构建结果列表
        List<MediaFileVO> voList = new ArrayList<>();
        final String finalJobName = jobName;
        final String finalWaylineName = waylineName;

        for (MediaFileEntity vFile : recordsV) {
            String droneName = droneNameCache.getOrDefault(vFile.getDrone(), "");
            String fileUrl = urlMap.getOrDefault(vFile.getObjectKey(), "");
            String smallUrl = urlMap.getOrDefault(vFile.getSmallPhoto(), "");

            MediaFileVO mediaFileVO = MediaFileVO.builder()
                    .id(vFile.getId())
                    .fileId(vFile.getFileId())
                    .fileName(vFile.getFileName())
                    .filePath(vFile.getFilePath())
                    .workspaceId(vFile.getWorkspaceId())
                    .fingerprint(vFile.getFingerprint())
                    .tinnyFingerprint(vFile.getTinnyFingerprint())
                    .objectKey(vFile.getObjectKey())
                    .fileUrl(fileUrl)
                    .smallFileUrl(smallUrl)
                    .subFileType(vFile.getSubFileType())
                    .isOriginal(vFile.getIsOriginal())
                    .drone(vFile.getDrone())
                    .payload(vFile.getPayload())
                    .jobId(vFile.getJobId())
                    .jobName(finalJobName)
                    .waylineName(finalWaylineName)
                    .droneName(droneName)
                    .createTime(vFile.getCreateTime())
                    .updateTime(vFile.getUpdateTime())
                    .build();

            // 设置对应的T文件URL
            String fileKey = extractFileKey(vFile);
            List<MediaFileEntity> matchingTFiles = tFileMap.getOrDefault(fileKey, new ArrayList<>());
            if (!matchingTFiles.isEmpty()) {
                String tUrls = matchingTFiles.stream()
                        .map(t -> urlMap.get(t.getObjectKey()))
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(","));
                String smallTUrls = matchingTFiles.stream()
                        .map(t -> urlMap.get(t.getSmallPhoto()))
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(","));
                mediaFileVO.setUrlT(tUrls);
                mediaFileVO.setSmallUrlT(smallTUrls);
            }
            voList.add(mediaFileVO);
        }

        long endTime = System.currentTimeMillis();
        log.info("任务文件查询完成，耗时: {}ms", endTime - startTime);

        return new PaginationData<>(voList, new Pagination(pageDataV.getCurrent(), pageDataV.getSize(), pageDataV.getTotal()));
    }

    /**
     * 批量查询指定任务的T文件
     */
    private Map<String, List<MediaFileEntity>> batchGetTFilesByJobId(String workspaceId, String jobId, Set<String> vFileKeys) {
        if (vFileKeys.isEmpty()) return new HashMap<>();

        List<MediaFileEntity> allTFiles = fileMapper.selectList(
                new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                        .eq(MediaFileEntity::getJobId, jobId)
                        .like(MediaFileEntity::getFileName, "%T_%"));

        return allTFiles.stream()
                .collect(Collectors.groupingBy(this::extractFileKey))
                .entrySet().stream()
                .filter(entry -> vFileKeys.contains(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    @Override
    public PaginationData<MediaFileVO> getAllMediaFilesPaginationByWithoutJobId(String workspaceId, long page, long pageSize) {
        long startTime = System.currentTimeMillis();
        log.info("查询工作空间文件 - workspaceId: {}, page: {}, pageSize: {}", workspaceId, page, pageSize);

        // 1. 查询V文件（分页）
        Page<MediaFileEntity> pageDataV = fileMapper.selectPage(
                new Page<>(page, pageSize),
                new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                        .like(MediaFileEntity::getFileName, "%V_%")
                        .orderByDesc(MediaFileEntity::getId));
        List<MediaFileEntity> recordsV = pageDataV.getRecords();

        if (recordsV.isEmpty()) {
            log.info("未找到V文件，直接返回空结果");
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination()
                            .setPage(pageDataV.getCurrent())
                            .setPageSize(pageDataV.getSize())
                            .setTotal(pageDataV.getTotal()));
        }

        // 2. 批量查询相关数据，避免 N+1 问题
        Set<String> allJobIds = recordsV.stream().map(MediaFileEntity::getJobId).collect(Collectors.toSet());
        Set<String> allDroneSns = recordsV.stream().map(MediaFileEntity::getDrone).collect(Collectors.toSet());

        // 批量查询 job 信息
        Map<String, WaylineJobDTO> jobMap = batchGetJobsByJobIds(workspaceId, allJobIds);

        // 批量查询 device 信息
        Map<String, String> droneNameMap = batchGetDroneNamesByDroneSns(allDroneSns);

        // 批量查询 wayline 信息
        Set<String> waylineIds = jobMap.values().stream()
                .map(WaylineJobDTO::getFileId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<String, String> waylineNameMap = batchGetWaylineNamesByWaylineIds(waylineIds);

        // 3. 只查询当前页V文件对应的T文件，使用位置匹配
        Set<String> vPositionKeys = recordsV.stream()
                .map(v -> v.getShootPositionLa() + "_" + v.getShootPositionLo() + "_" + v.getJobId())
                .collect(Collectors.toSet());

        Map<String, List<MediaFileEntity>> tFileMap = batchGetTFilesByPositionKeys(workspaceId, vPositionKeys);

        // 4. 批量生成 OSS URL
        Set<String> allObjectKeys = new HashSet<>();
        recordsV.forEach(v -> allObjectKeys.add(v.getObjectKey()));
        tFileMap.values().forEach(tFiles ->
                tFiles.forEach(t -> allObjectKeys.add(t.getObjectKey())));

        // 6. 生成URL
        if (allObjectKeys.isEmpty()) {
            log.info("没有需要生成URL的文件，直接返回空结果");
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination(pageDataV.getCurrent(), pageDataV.getSize(), pageDataV.getTotal()));
        }
        Map<String, String> urlMap = batchGenerateOssUrls(allObjectKeys);

        // 5. 构建结果列表
        List<MediaFileVO> voList = new ArrayList<>();

        for (MediaFileEntity vFile : recordsV) {
            MediaFileVO mediaFileVO = convertEntityToVoOptimized(vFile, jobMap, droneNameMap, waylineNameMap, urlMap);

            // 设置对应的T文件URL
            String positionKey = vFile.getShootPositionLa() + "_" + vFile.getShootPositionLo() + "_" + vFile.getJobId();
            List<MediaFileEntity> matchingTFiles = tFileMap.getOrDefault(positionKey, new ArrayList<>());
            if (!matchingTFiles.isEmpty()) {
                String tUrls = matchingTFiles.stream()
                        .map(t -> urlMap.get(t.getObjectKey()))
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(","));
                mediaFileVO.setUrlT(tUrls);
            }
            voList.add(mediaFileVO);
        }

        long endTime = System.currentTimeMillis();
        log.info("工作空间文件查询完成，耗时: {}ms", endTime - startTime);

        return new PaginationData<>(voList, new Pagination()
                .setPage(pageDataV.getCurrent())
                .setPageSize(pageDataV.getSize())
                .setTotal(pageDataV.getTotal()));
    }

    /**
     * 批量查询指定位置的T文件
     */
    private Map<String, List<MediaFileEntity>> batchGetTFilesByPositionKeys(String workspaceId, Set<String> vPositionKeys) {
        if (vPositionKeys.isEmpty()) return new HashMap<>();

        List<MediaFileEntity> allTFiles = fileMapper.selectList(
                new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                        .like(MediaFileEntity::getFileName, "%T_%"));

        return allTFiles.stream()
                .collect(Collectors.groupingBy(t ->
                        t.getShootPositionLa() + "_" + t.getShootPositionLo() + "_" + t.getJobId()))
                .entrySet().stream()
                .filter(entry -> vPositionKeys.contains(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    @Override
    public PaginationData<MediaFileVO> queryAllMediaFilesPaginationByWithoutJobId(String workspaceId, MediaQueryDTO queryDTO) {
        long startTime = System.currentTimeMillis();
        log.info("查询条件 - workspaceId: {}, droneSn: {}, waylineId: {}, timeStart: {}, timeEnd: {}, page: {}, pageSize: {}",
                workspaceId, queryDTO.getDroneSn(), queryDTO.getWaylineId(),
                queryDTO.getTimeStart(), queryDTO.getTimeEnd(), queryDTO.getPage(), queryDTO.getPageSize());

        // 1. 预处理 waylineId 查询条件，避免重复查询
        List<String> jobIds = null;
        if (queryDTO.getWaylineId() != null && !queryDTO.getWaylineId().isEmpty()) {
            List<WaylineJobEntity> waylineJobs = waylineJobMapper.selectList(
                    new LambdaQueryWrapper<WaylineJobEntity>()
                            .eq(WaylineJobEntity::getWorkspaceId, workspaceId)
                            .eq(WaylineJobEntity::getFileId, queryDTO.getWaylineId()));
            jobIds = waylineJobs.stream().map(WaylineJobEntity::getJobId).collect(Collectors.toList());
            if (jobIds.isEmpty()) {
                // 如果没有找到对应的任务，直接返回空结果
                return new PaginationData<>(new ArrayList<>(), new Pagination()
                        .setPage(queryDTO.getPage())
                        .setPageSize(queryDTO.getPageSize())
                        .setTotal(0));
            }
        }

        // 2. 构建 V 文件查询条件
        LambdaQueryWrapper<MediaFileEntity> queryWrapper = buildQueryWrapper(workspaceId, queryDTO, jobIds, "%V_%");

        log.info("V文件查询条件构建完成，准备执行查询");
        // 查询 V 文件（分页）
        Page<MediaFileEntity> pageDataV = fileMapper.selectPage(new Page<>(queryDTO.getPage(), queryDTO.getPageSize()), queryWrapper);
        log.info("V文件查询结果 - 总数: {}, 当前页数据: {}", pageDataV.getTotal(), pageDataV.getRecords().size());

        List<MediaFileEntity> recordsV = pageDataV.getRecords();
        if (recordsV.isEmpty()) {
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination()
                            .setPage(pageDataV.getCurrent())
                            .setPageSize(pageDataV.getSize())
                            .setTotal(pageDataV.getTotal()));
        }

        // 3. 批量查询相关数据，避免 N+1 问题
        Set<String> allJobIds = recordsV.stream().map(MediaFileEntity::getJobId).collect(Collectors.toSet());
        Set<String> allDroneSns = recordsV.stream().map(MediaFileEntity::getDrone).collect(Collectors.toSet());

        // 批量查询 job 信息
        Map<String, WaylineJobDTO> jobMap = batchGetJobsByJobIds(workspaceId, allJobIds);

        // 批量查询 device 信息
        Map<String, String> droneNameMap = batchGetDroneNamesByDroneSns(allDroneSns);

        // 批量查询 wayline 信息
        Set<String> waylineIds = jobMap.values().stream()
                .map(WaylineJobDTO::getFileId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<String, String> waylineNameMap = batchGetWaylineNamesByWaylineIds(waylineIds);

        // 4. 只查询需要的 T 文件，而不是全量查询
        Set<String> vFileKeys = recordsV.stream()
                .map(this::extractFileKey)
                .collect(Collectors.toSet());

        Map<String, List<MediaFileEntity>> tFileMap = batchGetTFilesByKeys(workspaceId, queryDTO, jobIds, vFileKeys);

        // 5. 批量生成 OSS URL
        Set<String> allObjectKeys = new HashSet<>();
        recordsV.forEach(v -> {
            allObjectKeys.add(v.getObjectKey());
            allObjectKeys.add(v.getSmallPhoto());
        });
        tFileMap.values().forEach(tFiles -> {
            tFiles.forEach(t -> {
                allObjectKeys.add(t.getObjectKey());
                allObjectKeys.add(t.getSmallPhoto());
            });
        });

        // 6. 生成URL
        if (allObjectKeys.isEmpty()) {
            log.info("没有需要生成URL的文件，直接返回空结果");
            return new PaginationData<>(new ArrayList<>(),
                    new Pagination(pageDataV.getCurrent(), pageDataV.getSize(), pageDataV.getTotal()));
        }
        Map<String, String> urlMap = batchGenerateOssUrls(allObjectKeys);

        // 6. 构建结果列表
        List<MediaFileVO> voList = new ArrayList<>();
        for (MediaFileEntity vFile : recordsV) {
            MediaFileVO mediaFileVO = convertEntityToVoOptimized(vFile, jobMap, droneNameMap, waylineNameMap, urlMap);

            // 设置 T 文件 URL
            String fileKey = extractFileKey(vFile);
            List<MediaFileEntity> matchingTFiles = tFileMap.getOrDefault(fileKey, new ArrayList<>());
            if (!matchingTFiles.isEmpty()) {
                String tUrls = matchingTFiles.stream()
                        .map(t -> urlMap.get(t.getObjectKey()))
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(","));
                String smallTUrls = matchingTFiles.stream()
                        .map(t -> urlMap.get(t.getSmallPhoto()))
                        .filter(Objects::nonNull)
                        .collect(Collectors.joining(","));
                mediaFileVO.setUrlT(tUrls);
                mediaFileVO.setSmallUrlT(smallTUrls);
            }
            voList.add(mediaFileVO);
        }

        long endTime = System.currentTimeMillis();
        log.info("查询完成，耗时: {}ms", endTime - startTime);

        return new PaginationData<>(voList, new Pagination()
                .setPage(pageDataV.getCurrent())
                .setPageSize(pageDataV.getSize())
                .setTotal(pageDataV.getTotal()));
    }

    /**
     * 构建通用查询条件
     */
    private LambdaQueryWrapper<MediaFileEntity> buildQueryWrapper(String workspaceId, MediaQueryDTO queryDTO, List<String> jobIds, String fileNamePattern) {
        LambdaQueryWrapper<MediaFileEntity> queryWrapper = new LambdaQueryWrapper<MediaFileEntity>()
                .eq(MediaFileEntity::getWorkspaceId, workspaceId)
                .like(MediaFileEntity::getFileName, fileNamePattern);

        if (queryDTO.getDroneSn() != null && !queryDTO.getDroneSn().isEmpty()) {
            queryWrapper.eq(MediaFileEntity::getDrone, queryDTO.getDroneSn());
        }

        if (jobIds != null && !jobIds.isEmpty()) {
            queryWrapper.in(MediaFileEntity::getJobId, jobIds);
        }

        if (queryDTO.getTimeStart() != null && queryDTO.getTimeStart() > 0) {
            queryWrapper.ge(MediaFileEntity::getCreateTime, queryDTO.getTimeStart());
        }

        if (queryDTO.getTimeEnd() != null && queryDTO.getTimeEnd() > 0) {
            queryWrapper.le(MediaFileEntity::getCreateTime, queryDTO.getTimeEnd());
        }

        queryWrapper.orderByDesc(MediaFileEntity::getId);
        return queryWrapper;
    }

    /**
     * 提取文件关键字
     */
    private String extractFileKey(MediaFileEntity file) {
        String fileName = file.getFileName();
        String pattern = fileName.contains("V_") ? "V_" : "T_";
        int startIndex = fileName.lastIndexOf(pattern) + 2;
        int endIndex = fileName.lastIndexOf(".");
        return fileName.substring(startIndex, endIndex) + file.getJobId();
    }

    /**
     * 批量查询 Job 信息
     */
    private Map<String, WaylineJobDTO> batchGetJobsByJobIds(String workspaceId, Set<String> jobIds) {
        if (jobIds.isEmpty()) return new HashMap<>();

        return jobIds.stream()
                .map(jobId -> jobService.getJobByJobId(workspaceId, jobId))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toMap(WaylineJobDTO::getJobId, job -> job));
    }

    /**
     * 批量查询设备名称
     */
    private Map<String, String> batchGetDroneNamesByDroneSns(Set<String> droneSns) {
        if (droneSns.isEmpty()) return new HashMap<>();

        Map<String, String> droneNameMap = new HashMap<>();
        for (String droneSn : droneSns) {
            Optional<DeviceDTO> droneDTO = deviceService.getDeviceBySn(droneSn);
            if (droneDTO.isPresent()) {
                droneNameMap.put(droneSn, droneDTO.get().getDeviceName());
            }
        }
        return droneNameMap;
    }

    /**
     * 批量查询航线名称
     */
    private Map<String, String> batchGetWaylineNamesByWaylineIds(Set<String> waylineIds) {
        if (waylineIds.isEmpty()) return new HashMap<>();

        Map<String, String> waylineNameMap = new HashMap<>();
        for (String waylineId : waylineIds) {
            WaylineFileEntity waylineFile = waylineFileService.getWaylineFileByWaylineId(waylineId);
            if (waylineFile != null) {
                waylineNameMap.put(waylineId, waylineFile.getName());
            }
        }
        return waylineNameMap;
    }

    /**
     * 批量查询 T 文件
     */
    private Map<String, List<MediaFileEntity>> batchGetTFilesByKeys(String workspaceId, MediaQueryDTO queryDTO, List<String> jobIds, Set<String> vFileKeys) {
        if (vFileKeys.isEmpty()) return new HashMap<>();

        LambdaQueryWrapper<MediaFileEntity> queryWrapperT = buildQueryWrapper(workspaceId, queryDTO, jobIds, "%T_%");
        List<MediaFileEntity> allTFiles = fileMapper.selectList(queryWrapperT);

        return allTFiles.stream()
                .collect(Collectors.groupingBy(this::extractFileKey))
                .entrySet().stream()
                .filter(entry -> vFileKeys.contains(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * 批量生成 OSS URL
     */
    private Map<String, String> batchGenerateOssUrls(Set<String> objectKeys) {
        Map<String, String> urlMap = new HashMap<>();
        if(objectKeys.isEmpty()) return new HashMap<>();
        for (String objectKey : objectKeys) {
            String url = ossFileService.getFileUrlByObjectKey(OssConfiguration.bucket, objectKey);
            urlMap.put(objectKey, url);
        }
        return urlMap;
    }

    /**
     * 优化版本的实体转 VO 方法
     */
    private MediaFileVO convertEntityToVoOptimized(MediaFileEntity entity,
                                                   Map<String, WaylineJobDTO> jobMap,
                                                   Map<String, String> droneNameMap,
                                                   Map<String, String> waylineNameMap,
                                                   Map<String, String> urlMap) {
        if (entity == null) return null;

        String jobId = entity.getJobId();
        WaylineJobDTO jobDTO = jobMap.get(jobId);
        String jobName = jobDTO != null ? jobDTO.getJobName() : "";
        String waylineName = "";
        if (jobDTO != null && jobDTO.getFileId() != null) {
            waylineName = waylineNameMap.getOrDefault(jobDTO.getFileId(), "");
        }
        String droneName = droneNameMap.getOrDefault(entity.getDrone(), "");
        String fileUrl = urlMap.getOrDefault(entity.getObjectKey(), "");
        String smallUrl = urlMap.getOrDefault(entity.getSmallPhoto(), "");

        return MediaFileVO.builder()
                .id(entity.getId())
                .fileId(entity.getFileId())
                .fileName(entity.getFileName())
                .filePath(entity.getFilePath())
                .workspaceId(entity.getWorkspaceId())
                .fingerprint(entity.getFingerprint())
                .tinnyFingerprint(entity.getTinnyFingerprint())
                .objectKey(entity.getObjectKey())
                .fileUrl(fileUrl)
                .smallFileUrl(smallUrl)
                .subFileType(entity.getSubFileType())
                .isOriginal(entity.getIsOriginal())
                .drone(entity.getDrone())
                .payload(entity.getPayload())
                .jobId(entity.getJobId())
                .jobName(jobName)
                .waylineName(waylineName)
                .droneName(droneName)
                .createTime(entity.getCreateTime())
                .updateTime(entity.getUpdateTime())
                .build();
    }

    /**
     * Convert MediaFileEntity to MediaFileVO
     *
     * @param entity
     * @return
     */
    private MediaFileVO convertEntityToVo(MediaFileEntity entity) {
        if (entity == null) {
            return null;
        }
        String workspaceId = entity.getWorkspaceId();
        String objectKey = entity.getObjectKey();
        String objectUrl = ossFileService.getFileUrlByObjectKey(OssConfiguration.bucket, objectKey);
        String jobId = entity.getJobId();
        Optional<WaylineJobDTO> jobDTO = jobService.getJobByJobId(workspaceId, jobId);
        String jobName = "";
        String droneName = "";
        String waylineName = "";
        if (!jobDTO.isEmpty()) {
            WaylineJobDTO waylineJobDTO = jobDTO.get();
            jobName = waylineJobDTO.getJobName();
            String waylineId = waylineJobDTO.getFileId();
            WaylineFileEntity waylineFile = waylineFileService.getWaylineFileByWaylineId(waylineId);
            waylineName = waylineFile.getName();
            Optional<DeviceDTO> droneDTO = deviceService.getDeviceBySn(entity.getDrone());
            if (!droneDTO.isEmpty()) {
                DeviceDTO drone = droneDTO.get();
                droneName = drone.getDeviceName();
            }
        }

        return MediaFileVO.builder()
                .id(entity.getId())
                .fileId(entity.getFileId())
                .fileName(entity.getFileName())
                .filePath(entity.getFilePath())
                .workspaceId(entity.getWorkspaceId())
                .fingerprint(entity.getFingerprint())
                .tinnyFingerprint(entity.getTinnyFingerprint())
                .objectKey(entity.getObjectKey())
                .fileUrl(objectUrl)
                .subFileType(entity.getSubFileType())
                .isOriginal(entity.getIsOriginal())
                .drone(entity.getDrone())
                .payload(entity.getPayload())
                .jobId(entity.getJobId())
                .jobName(jobName)
                .waylineName(waylineName)
                .droneName(droneName)
                .createTime(entity.getCreateTime())
                .updateTime(entity.getUpdateTime())
                .build();
    }

    private MediaUploadCallbackRequest convert2callbackRequest(FileUploadCallbackFile file) {
        if (Objects.isNull(file)) {
            return null;
        }
        return new MediaUploadCallbackRequest()
                .setExt(Optional.ofNullable(file.getExt())
                        .map(ext -> new MediaFileExtension()
                                .setDroneModelKey(ext.getDroneModelKey())
                                .setFileGroupId(ext.getFlightId())
                                .setOriginal(ext.getOriginal())
                                .setPayloadModelKey(ext.getPayloadModelKey()))
                        .orElse(new MediaFileExtension()))
                .setMetadata(Optional.ofNullable(file.getMetadata())
                        .map(data -> new MediaFileMetadata()
                                .setAbsoluteAltitude(data.getAbsoluteAltitude())
                                .setGimbalYawDegree(data.getGimbalYawDegree())
                                .setRelativeAltitude(data.getRelativeAltitude())
                                .setShootPosition(data.getShootPosition())
                                .setCreatedTime(data.getCreatedTime()))
                        .get())
                .setName(file.getName())
                .setObjectKey(file.getObjectKey())
                .setPath(file.getPath());
    }

    @Override
    public Integer batchDeleteMediaFiles(String workspaceId, List<String> fileIds) {
        if (fileIds == null || fileIds.isEmpty()) {
            return 0;
        }

        int deletedCount = 0;

        // 查询要删除的媒体文件信息
        LambdaQueryWrapper<MediaFileEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MediaFileEntity::getWorkspaceId, workspaceId)
                .in(MediaFileEntity::getFileId, fileIds);

        List<MediaFileEntity> mediaFiles = fileMapper.selectList(queryWrapper);

        if (mediaFiles.isEmpty()) {
            log.warn("No media files found for deletion in workspace: {}, fileIds: {}", workspaceId, fileIds);
            return 0;
        }

        // 批量删除数据库记录和OSS文件
        for (MediaFileEntity mediaFile : mediaFiles) {
            try {
                // 删除OSS文件
                if (StringUtils.hasText(mediaFile.getObjectKey())) {
                    Boolean ossDeleted = ossServiceContext.deleteObject(OssConfiguration.bucket, mediaFile.getObjectKey());
                    if (ossDeleted) {
                        log.info("Successfully deleted OSS file: {}", mediaFile.getObjectKey());
                    } else {
                        log.warn("Failed to delete OSS file: {}", mediaFile.getObjectKey());
                    }
                }

                // 删除数据库记录
                int dbDeleted = fileMapper.deleteById(mediaFile.getId());
                if (dbDeleted > 0) {
                    deletedCount++;
                    log.info("Successfully deleted media file from database: fileId={}, objectKey={}",
                            mediaFile.getFileId(), mediaFile.getObjectKey());
                } else {
                    log.warn("Failed to delete media file from database: fileId={}", mediaFile.getFileId());
                }

            } catch (Exception e) {
                log.error("Error deleting media file: fileId={}, objectKey={}, error={}",
                        mediaFile.getFileId(), mediaFile.getObjectKey(), e.getMessage(), e);
            }
        }

        log.info("Batch delete completed. Total requested: {}, Successfully deleted: {}", fileIds.size(), deletedCount);
        return deletedCount;
    }
}
